/*
 * Copyright 2024 ywcai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "defines.v"
`timescale 1ns/1ps

module dcache(
	input	wire						clk,
	input	wire						rst_n,

    input   wire                        req_i,
	input	wire[`MemAddrBus]			addr_i,
	input	wire						we_i,
	input	wire[7:0]					strb_i,
	input	wire[`RegDataBus]			wr_data_i,
	input	wire[511:0]					data_l2_i,
	input	wire						req_done_i,

	output	reg							req_o,
	output	reg							we_o,
	output	reg[`MemAddrBus]			addr_o,
	output	reg[`RegDataBus]			data_o,
	output	reg[`CacheBus]				cache_wr_o,
	output	wire[7:0]					bytes_req_o,
	output	wire[7:0]					bytes_offset_o,
	output	reg							req_grant_o,
	output	wire						d_load_stall_req_o
	);

	localparam IDLE		= 5'b00001;
	localparam MATCH	= 5'b00010;
	localparam COMM		= 5'b00100;
	localparam STORE		= 5'b01000;
	localparam DONE		= 5'b10000;

	//--------------------------------------------------------------------------
	reg[`MemAddrBus]	match_addr;
	reg[4:0]			cache_set;
	reg[31:0]			lru;	// the bank in the next dumpage
	reg[4:0]			cur_state, next_state;
	reg					cur_way;
	reg					stall_req;
	reg					previous_op;
	reg[`RegDataBus]	fresh_data;
	reg[7:0]			fresh_strb;
	reg					write_hit;
	integer				loop;

	//--------------------------------------------------------------------------
	//	{valid, dirty-no use, tag, data}
	reg					en_1;
	reg					we_1;
	wire[542:0]			rdata_bank_1;
	reg[542:0]			wdata_bank_1;

	gen_cache bank_1(
		.clk(clk),
		.rst_n(rst_n),
		.en_i(en_1),
		.addr_i(cache_set),
		.we_i(we_1),
		.wdata_i(wdata_bank_1),
		.rdata_o(rdata_bank_1)
	);

	reg					en_2;
	reg					we_2;
	wire[542:0]			rdata_bank_2;
	reg[542:0]			wdata_bank_2;

	gen_cache bank_2(
		.clk(clk),
		.rst_n(rst_n),
		.en_i(en_2),
		.addr_i(cache_set),
		.we_i(we_2),
		.wdata_i(wdata_bank_2),
		.rdata_o(rdata_bank_2)
	);

	assign d_load_stall_req_o = stall_req;

	wire addr_valid = (addr_i < `ADDR_END)
		&& !(addr_i >= `ROM_BASE && addr_i < `UART_BASE);

	wire i_sdram = (match_addr >= `SDRAM_BASE) && (match_addr < `ADDR_END);

	wire[`MemAddrBus] aligned_start = (match_addr >> 6) << 6;
	wire[`MemAddrBus] addr_start = i_sdram ? (
		(aligned_start < `SDRAM_BASE) ? `SDRAM_BASE : aligned_start)
		: match_addr;

	wire[`MemAddrBus] aligned_end = (((match_addr + 64) >> 6) << 6) - 1;
	wire[`MemAddrBus] addr_end =
		(aligned_end >= `ADDR_END) ? (`ADDR_END - 1) : aligned_end;

	assign bytes_req_o = i_sdram ? (addr_end - addr_start + 1) : 8'h8;
	assign bytes_offset_o = i_sdram ? (aligned_start < `SDRAM_BASE
		? {2'b00, addr_start[5:0]} : 8'h0) : {2'b00, match_addr[5:0]};

	wire[4:0] set_idx = match_addr[10:6];
	wire[28:0] tag_addr = match_addr[39:11];
	wire[2:0] offset = match_addr[5:3];

	wire[28:0] tag_way1 = rdata_bank_1[540:512];
	wire[28:0] tag_way2 = rdata_bank_2[540:512];
	wire valid_way1 = rdata_bank_1[542];
	wire valid_way2 = rdata_bank_2[542];

	wire hit_way1 = (tag_addr == tag_way1) & valid_way1;
	wire hit_way2 = (tag_addr == tag_way2) & valid_way2;

	wire[`RegDataBus] data_way1 =
        ({`XLEN{offset == 3'b000}} & rdata_bank_1[63:0])
		| ({`XLEN{offset == 3'b001}} & rdata_bank_1[127:64])
		| ({`XLEN{offset == 3'b010}} & rdata_bank_1[191:128])
		| ({`XLEN{offset == 3'b011}} & rdata_bank_1[255:192])
		| ({`XLEN{offset == 3'b100}} & rdata_bank_1[319:256])
		| ({`XLEN{offset == 3'b101}} & rdata_bank_1[383:320])
		| ({`XLEN{offset == 3'b110}} & rdata_bank_1[447:384])
		| ({`XLEN{offset == 3'b111}} & rdata_bank_1[511:448]);

	wire[`RegDataBus] data_way2 =
		({`XLEN{offset == 3'b000}} & rdata_bank_2[63:0])
		| ({`XLEN{offset == 3'b001}} & rdata_bank_2[127:64])
		| ({`XLEN{offset == 3'b010}} & rdata_bank_2[191:128])
		| ({`XLEN{offset == 3'b011}} & rdata_bank_2[255:192])
		| ({`XLEN{offset == 3'b100}} & rdata_bank_2[319:256])
		| ({`XLEN{offset == 3'b101}} & rdata_bank_2[383:319])
		| ({`XLEN{offset == 3'b110}} & rdata_bank_2[447:384])
		| ({`XLEN{offset == 3'b111}} & rdata_bank_2[511:448]);

	always @(posedge clk) begin
	    if (rst_n == `RESET_ENABLE) begin
			cur_state <= IDLE;
		end else begin
			cur_state <= next_state;
		end
	end

	always @(posedge clk) begin
	    if (rst_n == `RESET_ENABLE) begin
			match_addr <= `ZERO_ADDR;
			previous_op <= `DISABLE;
			fresh_data <= `XLEN'h0;
		end else begin
			if (req_i && addr_valid && req_grant_o) begin
				match_addr <= addr_i;
				previous_op <= we_i;
				fresh_data <= wr_data_i;
				fresh_strb <= strb_i;
			end
		end
	end

	// request should only sustain one cycle
	// write-through schema to L2 cache
	always @(*) begin
		if (rst_n == `RESET_ENABLE) begin
			next_state = IDLE;
			req_o = `DISABLE;
			addr_o = `ZERO_ADDR;
			we_o = `DISABLE;
			data_o = `XLEN'h0;
			cur_way = `DISABLE;
			lru = 32'h0;
			stall_req = `DISABLE;
			en_1 = `DISABLE;
			en_2 = `DISABLE;
			we_1 = `DISABLE;
			we_2 = `DISABLE;
			write_hit = `DISABLE;
		end else begin
			case (cur_state)
				IDLE: begin
					if (req_i && addr_valid) begin
						req_grant_o = `ENABLE;
						cache_set = addr_i[10:6];
						en_1 = `ENABLE;
						we_1 = `DISABLE;
						en_2 = `ENABLE;
						we_2 = `DISABLE;
						next_state = MATCH;
					end else begin
						req_grant_o = `DISABLE;
						next_state = IDLE;
					end
				end

				MATCH: begin
					if (!hit_way1 && !hit_way2) begin
						addr_o = addr_start;
						req_o = `ENABLE;
						we_o = `DISABLE;
						stall_req = `ENABLE;
						req_grant_o = `DISABLE;
						next_state = COMM;
					end else begin
						if (!previous_op) begin
							if (hit_way1) begin
								data_o = data_way1;
								lru[set_idx] = `ENABLE;
							end else begin
								data_o = data_way2;
								lru[set_idx] = `DISABLE;
							end

							we_1 = `DISABLE;
							we_2 = `DISABLE;
							if (req_i && addr_valid) begin
								req_grant_o = `ENABLE;
								cache_set = addr_i[10:6];
								en_1 = `ENABLE;
								en_2 = `ENABLE;
								next_state = MATCH;
							end else begin
								req_grant_o = `DISABLE;
								en_1 = `DISABLE;
								en_2 = `DISABLE;
								next_state = IDLE;
							end
						end else begin
							req_grant_o = `DISABLE;
							stall_req = `ENABLE;
							if (hit_way1) begin
								cur_way = `DISABLE;
								lru[set_idx] = `ENABLE;

								en_1 = `ENABLE;
								we_1 = `ENABLE;
								en_2 = `DISABLE;
								we_2 = `DISABLE;
								wdata_bank_1 = {2'b10, tag_addr, rdata_bank_1};
								if (offset == 3'b000) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_1[(loop<<3) +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b001) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_1[(loop<<3)+64 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b010) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_1[(loop<<3)+128 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b011) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_1[(loop<<3)+192 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b100) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_1[(loop<<3)+256 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b101) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_1[(loop<<3)+320 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b110) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_1[(loop<<3)+384 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_1[(loop<<3)+448 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end
							end else begin
								cur_way = `ENABLE;
								lru[set_idx] = `DISABLE;

								en_1 = `DISABLE;
								we_1 = `DISABLE;
								en_2 = `ENABLE;
								we_2 = `ENABLE;
								wdata_bank_2 = {2'b10, tag_addr, rdata_bank_2};
								if (offset == 3'b000) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_2[(loop<<3) +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b001) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_2[(loop<<3)+64 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b010) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_2[(loop<<3)+128 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b011) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_2[(loop<<3)+192 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b100) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_2[(loop<<3)+256 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b101) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_2[(loop<<3)+320 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else if (offset == 3'b110) begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_2[(loop<<3)+384 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end else begin
									for (loop = 0; loop < 8; loop = loop + 1) begin
										if (fresh_strb[loop]) begin
											wdata_bank_2[(loop<<3)+448 +: 8] = fresh_data[(loop<<3) +: 8];
										end
									end
								end
							end
							next_state = STORE;
						end
					end
				end

				COMM: begin
					if (req_done_i) begin
						if (!write_hit) begin
							cache_set = match_addr[10:6];

							if (!previous_op) begin
								if (offset == 3'b000) begin
									data_o = data_l2_i[63:0];
								end else if (offset == 3'b001) begin
									data_o = data_l2_i[127:64];
								end else if (offset == 3'b010) begin
									data_o = data_l2_i[191:128];
								end else if (offset == 3'b011) begin
									data_o = data_l2_i[255:192];
								end else if (offset == 3'b100) begin
									data_o = data_l2_i[319:256];
								end else if (offset == 3'b101) begin
									data_o = data_l2_i[383:320];
								end else if (offset == 3'b110) begin
									data_o = data_l2_i[447:384];
								end else begin
									data_o = data_l2_i[511:448];
								end
							end

							if (!lru[set_idx]) begin
								cur_way = `DISABLE;

								en_1 = `ENABLE;
								we_1 = `ENABLE;
								en_2 = `DISABLE;
								we_2 = `DISABLE;
								wdata_bank_1 = {2'b10, tag_addr, data_l2_i};
								if (!previous_op) begin
									next_state = DONE;
								end else begin
									if (offset == 3'b000) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_1[(loop<<3) +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b001) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_1[(loop<<3)+64 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b010) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_1[(loop<<3)+128 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b011) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_1[(loop<<3)+192 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b100) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_1[(loop<<3)+256 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b101) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_1[(loop<<3)+320 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b110) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_1[(loop<<3)+384 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_1[(loop<<3)+448 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end
									next_state = STORE;
								end
							end else begin
								cur_way = `ENABLE;

								en_1 = `DISABLE;
								we_1 = `DISABLE;
								en_2 = `ENABLE;
								we_2 = `ENABLE;
								wdata_bank_2 = {2'b10, tag_addr, data_l2_i};
								if (!previous_op) begin
									next_state = DONE;
								end else begin
									if (offset == 3'b000) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_2[(loop<<3) +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b001) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_2[(loop<<3)+64 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b010) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_2[(loop<<3)+128 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b011) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_2[(loop<<3)+192 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b100) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_2[(loop<<3)+256 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b101) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_2[(loop<<3)+320 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else if (offset == 3'b110) begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_2[(loop<<3)+384 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end else begin
										for (loop = 0; loop < 8; loop = loop + 1) begin
											if (fresh_strb[loop]) begin
												wdata_bank_2[(loop<<3)+448 +: 8] = fresh_data[(loop<<3) +: 8];
											end
										end
									end
									next_state = STORE;
								end
							end	
						end else begin
							next_state = DONE;
						end
					end else begin
						req_o = `DISABLE;
					end
				end

				STORE: begin
					req_o = `ENABLE;
					we_o = `ENABLE;
					addr_o = {tag_addr, set_idx, 6'h0};
					if (!cur_way) begin
						cache_wr_o = wdata_bank_1;
					end else begin
						cache_wr_o = wdata_bank_2;
					end
					write_hit = `ENABLE;
					next_state = COMM;
				end

				DONE: begin
					if (!cur_way) begin
						lru[set_idx] = `ENABLE;
					end else begin
						lru[set_idx] = `DISABLE;
					end
					en_1 = `DISABLE;
					we_1 = `DISABLE;
					en_2 = `DISABLE;
					we_2 = `DISABLE;
					write_hit = `DISABLE;
					stall_req = `DISABLE;
					next_state = IDLE;
				end

				default: begin
					next_state = IDLE;
				end
			endcase
		end
	end

endmodule
